using System;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Text;

namespace PhotoGallery.Core.Business
{
    [Serializable]
    public abstract class BusinessBase<TYPE, KEY> : INotifyPropertyChanged where TYPE : BusinessBase<TYPE, KEY>, new()
    {
        #region Properties

        private KEY id;
        /// <summary>
        /// Gets the unique Identification of the object.
        /// </summary>
        public KEY Id
        {
            get { return id; }
            set
            {
                if (Object.Equals(id, null) || !id.Equals(value)) { MarkChanged("Id"); id = value; }
            }
        }

        private DateTime dateCreated = DateTime.MinValue;
        /// <summary>
        /// The date on which the instance was created.
        /// </summary>
        public DateTime DateCreated
        {
            get { return dateCreated; }
            set
            {
                if (dateCreated != value)
                {
                    MarkChanged("DateCreated");
                    dateCreated = value;
                }
            }
        }

        #endregion

        #region IsNew, IsDeleted, IsChanged

        private bool isNew = true;
        /// <summary>
        /// Gets if this is a new object, False if it is a pre-existing object.
        /// </summary>
        public bool IsNew { get { return isNew; } }

        private bool isDeleted;
        /// <summary>
        /// Gets if this object is marked for deletion.
        /// </summary>
        public bool IsDeleted { get { return isDeleted; } }

        private bool isChanged;
        /// <summary>
        /// Gets if this object's data has been changed.
        /// </summary>
        public virtual bool IsChanged { get { return isChanged; } protected set { isChanged = value; } }

        /// <summary>
        /// Marks the object for deletion. It will then be deleted when the object's Save() method is called.
        /// </summary>
        public void DeleteOnSave()
        {
            isDeleted = true;
            IsChanged = true;
        }

        private StringCollection changedProperties = new StringCollection();

        /// <summary>
        /// A collection of the properties that have been marked as being dirty.
        /// </summary>
        public StringCollection ChangedProperties { get { return changedProperties; } }

        /// <summary>
        /// Marks an object as being dirty, or changed.
        /// </summary>
        /// <param name="propertyName">The name of the property to mark dirty.</param>
        protected virtual void MarkChanged(string propertyName)
        {
            IsChanged = true;

            if (!changedProperties.Contains(propertyName))
            {
                changedProperties.Add(propertyName);
            }

            OnPropertyChanged(propertyName);
        }

        /// <summary>
        /// Marks the object as being an clean, which means not dirty.
        /// </summary>
        public virtual void MarkOld()
        {
            isNew = false;
            IsChanged = false;
            changedProperties.Clear();
        }

        #endregion

        #region Validation

        private StringDictionary brokenRules = new StringDictionary();
        /// <summary>
        /// Get the broken rules of the entity.
        /// </summary>
        internal StringDictionary BrokenRules
        {
            get { return brokenRules; }
        }

        /// <summary>
        /// Add or remove a broken rule.
        /// </summary>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="errorMessage">The description of the error</param>
        /// <param name="isBroken">True if the validation rule is broken.</param>
        protected virtual void AddRule(string propertyName, string errorMessage, bool isBroken)
        {
            if (isBroken)
            {
                brokenRules[propertyName] = errorMessage;
            }
            else
            {
                if (brokenRules.ContainsKey(propertyName))
                {
                    brokenRules.Remove(propertyName);
                }
            }
        }

        /// <summary>
        /// Reinforces the business rules by adding additional rules to the broken rules collection.
        /// </summary>
        protected abstract void ValidationRules();

        /// <summary>
        /// Gets whether the object is valid or not.
        /// </summary>
        public bool IsValid
        {
            get
            {
                ValidationRules();
                return brokenRules.Count == 0;
            }
        }

        /// <summary>
        /// If the object has broken business rules, use this property to get access to the different validation messages.
        /// </summary>
        public virtual string ValidationMessage
        {
            get
            {
                if (!IsValid)
                {
                    StringBuilder sb = new StringBuilder();

                    foreach (string msg in brokenRules.Values)
                    {
                        sb.AppendLine(msg);
                    }

                    return sb.ToString();
                }

                return string.Empty;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Loads an instance of the object based on the Id.
        /// </summary>
        /// <param name="id">The unique identifier of the object</param>
        public static TYPE Load(KEY id)
        {
            TYPE instance = new TYPE();

            instance = instance.DataSelect(id);

            if (instance != null)
            {
                instance.Id = id;
                instance.MarkOld();

                return instance;
            }

            return null;
        }

        /// <summary>
        /// Saves the object to the data store (inserts, updates or deletes).
        /// </summary>
        /// <returns></returns>
        public virtual SaveAction Save()
        {
            if (!IsValid && !IsDeleted)
            {
                throw new InvalidOperationException(ValidationMessage);
            }

            if (IsChanged)
            {
                SaveAction action = Update();

                return action;
            }

            return SaveAction.None;
        }

        /// <summary>
        /// Is called by the save method when the object is old and dirty.
        /// </summary>
        private SaveAction Update()
        {
            SaveAction action = SaveAction.None;

            if (IsDeleted)
            {
                if (!IsNew)
                {
                    action = SaveAction.Delete;

                    OnSaving(this, action);
                    DataDelete();
                }
            }
            else
            {
                if (IsNew)
                {
                    action = SaveAction.Insert;

                    dateCreated = DateTime.Now;

                    OnSaving(this, action);
                    DataInsert();
                }
                else
                {
                    action = SaveAction.Update;

                    OnSaving(this, action);
                    DataUpdate();
                }

                MarkOld();
            }

            OnSaved(this, action);

            return action;
        }

        #endregion

        #region Data access

        /// <summary>
        /// Retrieves the object from the data store and populates it.
        /// </summary>
        /// <param name="id">The unique identifier of the object.</param>
        /// <returns>True if the object exists and is being populated successfully</returns>
        protected abstract TYPE DataSelect(KEY id);

        /// <summary>
        /// Updates the object in its data store.
        /// </summary>
        protected abstract void DataUpdate();

        /// <summary>
        /// Inserts a new object to the data store.
        /// </summary>
        protected abstract void DataInsert();

        /// <summary>
        /// Deletes the object from the data store.
        /// </summary>
        protected abstract void DataDelete();

        #endregion

        #region Equality overrides

        /// <summary>
        /// A uniquely key to identify this particullar instance of the class
        /// </summary>
        /// <returns>A unique integer value</returns>
        public override int GetHashCode()
        {
            return this.Id.GetHashCode();
        }

        /// <summary>
        /// Comapares this object with another
        /// </summary>
        /// <param name="obj">The object to compare</param>
        /// <returns>True if the two objects as equal</returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (obj.GetType() == this.GetType())
            {
                return obj.GetHashCode() == this.GetHashCode();
            }

            return false;
        }

        /// <summary>
        /// Checks to see if two business objects are the same.
        /// </summary>
        public static bool operator ==(BusinessBase<TYPE, KEY> first, BusinessBase<TYPE, KEY> second)
        {
            if (Object.ReferenceEquals(first, second))
            {
                return true;
            }

            if ((object)first == null || (object)second == null)
            {
                return false;
            }

            return first.GetHashCode() == second.GetHashCode();
        }

        /// <summary>
        /// Checks to see if two business objects are different.
        /// </summary>
        public static bool operator !=(BusinessBase<TYPE, KEY> first, BusinessBase<TYPE, KEY> second)
        {
            return !(first == second);
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when the class is Saved
        /// </summary>
        public static event EventHandler<SavedEventArgs> Saved;

        /// <summary>
        /// Raises the Saved event.
        /// </summary>
        /// <param name="action"></param>
        protected static void OnSaved(BusinessBase<TYPE, KEY> sender, SaveAction action)
        {
            if (Saved != null)
            {
                Saved(sender, new SavedEventArgs { Action = action });
            }
        }

        /// <summary>
        /// Occurs when the class is beening Saved
        /// </summary>
        public static event EventHandler<SavedEventArgs> Saving;

        /// <summary>
        /// Raises the Saving event.
        /// </summary>
        protected static void OnSaving(BusinessBase<TYPE, KEY> sender, SaveAction action)
        {
            if (Saving != null)
            {
                Saving(sender, new SavedEventArgs { Action = action });
            }
        }

        /// <summary>
        /// Occurs when this instance is marked dirty. 
        /// It means the instance has been changed but not saved.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}